package org.budo.phone.freeswitch;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.budo.phone.api.CallCenterAgent;
import org.budo.phone.api.CallCenterQueue;
import org.budo.phone.api.CallCenterTier;
import org.budo.phone.freeswitch.util.EslUtil;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.StringUtil;
import org.freeswitch.esl.client.inbound.Client;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.freeswitch.esl.client.transport.message.EslMessage;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
@Getter
@Setter
@NoArgsConstructor
public abstract class AbstractFreeswitchEslClient implements FreeswitchEslClient {
    private String host;

    private Integer port;

    private String password;

    private transient Client eslClient;

    public AbstractFreeswitchEslClient(String host, Integer port, String password) {
        this.setHost(host);
        this.setPort(port);
        this.setPassword(password);
    }

    @Override
    public void close() {
        log.info("#34 close, this=" + this);

        EslUtil.closeQuietly(this.getEslClient()); // 释放旧的资源
    }

    @Override
    public boolean canSend() {
        boolean canSend = EslUtil.canSend(this.getEslClient());
        if (canSend) {
            log.debug("#55 canSend=" + canSend + ", this=" + this);
        } else {
            log.warn("#57 canSend=" + canSend + ", this=" + this);
        }

        return canSend; // 检查esl连接是否断开
    }

    @Override
    public Boolean sofiaStatusProfileInternalReg(String authUser) {
        String command = "sofia status profile internal reg " + authUser;
        EslMessage message = this.sendSyncApiCommand(command, "");

        if (null == message) {
            log.error("#57 callCenterAgentGetState return null, message=" + message + ", command=" + command);
            return null;
        }

        List<String> bodyLines = message.getBodyLines();
        String lines = StringUtil.join(bodyLines);
        lines = lines.replaceAll(" ", "").replaceAll("\t", "");
        return lines.contains(",Auth-User:" + authUser + ",");
    }

    @Override
    public String findCallIdByUUID(String uuid) {
        String command = "uuid_getvar " + uuid + " 'call_uuid'";
        EslMessage message = this.sendSyncApiCommand(command, "");

        if (null == message) {
            log.error("#57 findCallIdByUUID return null, message=" + message + ", command=" + command);
            return null;
        }

        List<String> bodyLines = message.getBodyLines();
        String firstLine = ListUtil.first(bodyLines);
        if (firstLine.contains("-ERR")) {
            return null;
        }
        return StringUtil.trim(firstLine);
    }

    @Override
    public String callCenterAgentGetState(String user) {
        String command = "callcenter_config agent get state " + user;
        EslMessage message = this.sendSyncApiCommand(command, "");

        if (null == message) {
            log.error("#66 callCenterAgentGetState return null, message=" + message + ", command=" + command);
            return null;
        }

        List<String> bodyLines = message.getBodyLines();
        String firstLine = ListUtil.first(bodyLines);
        return StringUtil.trim(firstLine);
    }

    @Override
    public void callCenterTierAdd(String agentName, String queueName) {
        String command = "callcenter_config tier add ";
        String arg = queueName + " " + agentName + " 1" + " 1";
        this.sendApiCommand(command, arg); // callcenter_config tier add queue@001 70001 1 1
    }

    @Override
    public void callCenterAgentSetContact(String agentName) {
        String userName = "user/" + agentName; // user/70001
        String command = "callcenter_config agent set ";
        String arg = "contact " + agentName + " " + userName;
        this.sendApiCommand(command, arg); // callcenter_config agent set contact 70001 user/70001
    }

    @Override
    public void callCenterAgentSetWrapUptime(String agentName, int time) {
        String command = "callcenter_config agent set wrap_up_time ";
        String arg = agentName + " " + time;
        this.sendApiCommand(command, arg); // callcenter_config agent add 70001 callback
    }

    @Override
    public void callCenterAgentSetRejectDelayTime(String agentName, int time) {
        String command = "callcenter_config agent set reject_delay_time ";
        String arg = agentName + " " + time;
        this.sendApiCommand(command, arg); // callcenter_config agent add 70001 callback
    }

    @Override
    public void callCenterAgentSetBusyDelayTime(String agentName, int time) {
        String command = "callcenter_config agent busy_delay_time ";
        String arg = agentName + " " + time;
        this.sendApiCommand(command, arg); // callcenter_config agent add 70001 callback
    }

    @Override
    public void callCenterAgentAdd(String agentName) {
        String command = "callcenter_config agent add ";
        String arg = agentName + " callback";
        this.sendApiCommand(command, arg); // callcenter_config agent add 70001 callback
    }

    @Override
    public void UUIDFileMan(String uuid, String state) {
        String command = String.format("uuid_fileman %s ", uuid);
        this.sendApiCommand(command, state);
    }

    /**
     * 迁出
     */
    @Override
    public void callCenterAgentDel(String number) {
        String agentName = number /* + "@default" */; // 70001@default
        String command = "callcenter_config agent del ";
        this.sendApiCommand(command, agentName);
    }

    /**
     * 示闲示忙
     */
    @Override
    public void callCenterAgentSetStatus(String agentName, String status) {
        String command = "callcenter_config agent set status " + agentName + " " + status;
        this.sendApiCommand(command, ""); // callcenter_config agent set status 70001 Available
    }

    @Override
    public void callCenterAgentSetState(final String agentName, String state) {
        String command = "callcenter_config agent set state " + agentName + " " + state;
        this.sendApiCommand(command, ""); // callcenter_config agent set state 70001 Waiting
    }

    @Override
    public void uuidKill(String uuid) {
        if (StringUtil.isEmpty(uuid)) {
            log.error("#128 uuidKill, uuid=" + uuid);
            return;
        }

        String command = "uuid_kill " + uuid;
        this.sendApiCommand(command, "");
    }

    @Override
    public String callCenterGetAgentUuid(String user) {
        String command = "callcenter_config agent get uuid " + user;
        EslMessage message = this.sendSyncApiCommand(command, "");
        if (null == message) {
            log.error("#135 callCenterAgentGetUuid, return null, message=" + message + ", command=" + command + ", this=" + this);
            return null;
        }

        String uuid = StringUtil.join(message.getBodyLines(), "");
        if ("-ERR Agent not found!".equals(uuid) || "-ERR no reply".equals(uuid)) {
            log.error("#142 callCenterAgentGetUuid, return null, message=" + message + ", command=" + command + ", uuid=" + uuid + ", this=" + this);
            return null;
        }

        return uuid;
    }

    @Override
    public Map<String, CallCenterTier> callCenterTierList() {
        String command = "callcenter_config tier list";
        EslMessage message = this.sendSyncApiCommand(command, null);

        if (null == message) {
            log.error("#137 callCenterAgentGetState return null, message=" + message + ", command=" + command + ", this=" + this);
            return null;
        }

        Map<String, CallCenterTier> map = new HashMap<String, CallCenterTier>();

        List<String> bodyLines = message.getBodyLines();
        for (int i = 1; i < bodyLines.size() - 1; i++) { // 舍弃首行头，舍弃最后一行的+OK
            String line = bodyLines.get(i);
            String[] array = line.split("\\|");

            if (array.length < 4) { // 这个4可能还要调
                log.error("#132 line=" + line + ", command=" + command);
                continue;
            }

            String agent = array[1];

            CallCenterTier tier = new CallCenterTier() //
                    .setQueue(array[0]) //
                    .setAgent(agent) //
                    .setState(array[2]) //
                    .setLevel(array[3]) //
                    .setPosition(array[4]);
            map.put(agent, tier);
        }

        return map;
    }

    @Override
    public Map<String, CallCenterAgent> callCenterAgentList(String queueName) {
        String command = "callcenter_config agent list";
        if (StringUtil.isNotBlank(queueName)) {
            command = "callcenter_config queue list agents " + queueName;
        }
        EslMessage message = this.sendSyncApiCommand(command, null);

        if (null == message) {
            log.error("#162 callCenterAgentGetState return null, message=" + message + ", command=" + command);
            return null;
        }

        Map<String, CallCenterAgent> map = new HashMap<String, CallCenterAgent>();

        List<String> bodyLines = message.getBodyLines();
        for (int i = 1; i < bodyLines.size() - 1; i++) { // 舍弃首行头，舍弃最后一行的+OK
            String line = bodyLines.get(i);
            String[] array = line.split("\\|");

            if (array.length < 6) { // 这个6可能还要调
                log.error("#163 line=" + line + ", command=" + command);
                continue;
            }

            String name = array[0];

            CallCenterAgent agent = new CallCenterAgent() //
                    .setName(name) //
                    .setSystem(array[1]) //
                    .setType(array[3]) //
                    .setContact(array[4]) //
                    .setStatus(array[5]) //
                    .setState(array[6]) // 
                    .setMaxNoAnswer(array[7]) // 
                    .setWrapUpTime(array[8]) // 
                    .setRejectDelayTime(array[9]) // 
                    .setBusyDelayTime(array[10]) // 
                    .setNoAnswerDelayTime(array[11]) // 
                    .setLastBridgeStart(array[12]) // 
                    .setLastBridgeEnd(array[13]) // 
                    .setLastOfferedCall(array[14]) // 
                    .setLastStatusChange(array[15]) // 
                    .setNoAnswerCount(array[16]) // 
                    .setCallsAnswered(array[17]) // 
                    .setTalkTime(array[18]) // 
                    .setReadyTime(array[19]);
            map.put(name, agent);
        }

        return map;
    }

    @Override
    public Map<String, CallCenterQueue> callCenterQueueList() {
        String command = "callcenter_config queue list";
        EslMessage message = this.sendSyncApiCommand(command, "");

        if (null == message) {
            log.error("#199 callCenterAgentGetState return null, message=" + message + ", command=" + command);
            return null;
        }

        Map<String, CallCenterQueue> map = new TreeMap<String, CallCenterQueue>();

        List<String> bodyLines = message.getBodyLines();
        for (int i = 1; i < bodyLines.size() - 1; i++) { // 舍弃首行头，舍弃最后一行的+OK

            String line = bodyLines.get(i);
            String[] array = line.split("\\|");

            String name = array[0];
            String strategy = array[1];
            String mohSound = array[2];
            String timeBaseScore = array[3];
            String tierRulesApply = array[4];
            String tierRuleWaitSecond = array[5];
            String tierRuleWaitMultiplyLevel = array[6];
            String tierRuleNoAgentNoWait = array[7];
            String discardAbandonedAfter = array[8];
            String abandonedResumeAllowed = array[9];
            String maxWaitTime = array[10];
            String maxWaitTimeWithNoAgent = array[11];
            String maxWaitTimeWithNoAgentTimeReached = array[12];
            String recordTemplate = array[13];
            String callsAnswered = array[14];
            String callsAbandoned = array[15];
            String ringProgressivelyDelay = array[16];
            CallCenterQueue agent = new CallCenterQueue().setName(name).setStrategy(strategy).setMohSound(mohSound).setTimeBaseScore(timeBaseScore).setTierRulesApply(tierRulesApply).setTierRuleWaitSecond(tierRuleWaitSecond).setTierRuleWaitMultiplyLevel(tierRuleWaitMultiplyLevel).setTierRuleNoAgentNoWait(tierRuleNoAgentNoWait).setDiscardAbandonedAfter(discardAbandonedAfter).setAbandonedResumeAllowed(abandonedResumeAllowed).setMaxWaitTime(maxWaitTime).setMaxWaitTimeWithNoAgent(maxWaitTimeWithNoAgent).setMaxWaitTimeWithNoAgentTimeReached(maxWaitTimeWithNoAgentTimeReached).setRecordTemplate(recordTemplate).setCallsAnswered(callsAnswered).setCallsAbandoned(callsAbandoned).setRingProgressivelyDelay(ringProgressivelyDelay);

            map.put(name, agent);
        }

        return map; // TreeMap 排序
    }

    @Override
    public void sendApiCommand(String command, String arg) {
        EslMessage message = this.sendSyncApiCommand(command, arg);

        if (null == message) {
            log.error("#241 callCenterAgentGetState return null, message=" + message + ", command=" + command);
        }
    }

    @Override
    public void sendBgApiCommand(String command, String arg) {
        String s = this.sendAsyncApiCommand(command, arg);

        if (null == s) {
            log.error("sendBgApiCommand message=" + s + ", command=" + command);
        }
    }

    @Override
    public boolean uuidExists(String callId) {
        String command = "uuid_exists " + callId;
        EslMessage message = this.sendSyncApiCommand(command, null);
        if (null == message) {
            log.error("#169 generateCallUUID return null, message=" + message + ", command=" + command);
            return false;
        }

        return "false".equals(message.getBodyLines().get(0));
    }

    /**
     * 连接ESL，如果已连接就直接返回，判断假死重连
     */
    protected void assertInited_1() {
        if (null != this.getEslClient() && this.canSend()) {
            return;
        }

        final int timeout = 10; // 超时 Seconds

        Client client = this.connectEslClient(this.getHost(), //
                this.getPort(), //
                this.getPassword(), //
                timeout);

        this.setEslClient(client); // 这里用自己实现接口方法收消息，再转给传入的listener，方便以eslClient为单位做超时重连
    }

    private EslMessage sendSyncApiCommand(String command, String arg) {
        Client eslClient = this.getEslClient();

        if (null == eslClient) {
            log.error("#235 sendSyncApiCommand error, eslClient=" + eslClient + ", command=" + command + ", arg=" + arg + ", host=" + this.getHost() + ", port=" + this.getPort() + ", this=" + this);
            return null;
        }

        EslMessage eslMessage = null;
        try {
            eslMessage = this.sendApiCommand(eslClient, command, arg);
        } catch (Throwable e) {
            throw new RuntimeException("#238 command=" + command + ", host=" + this.getHost() + ", port=" + this.getPort() + ", e=" + e, e);
        }

        if (log.isDebugEnabled()) {
            log.debug("#242 command=" + command + ", arg=" + arg + ", host=" + this.getHost() + ", port=" + this.getPort() + ", eslMessage=" + eslMessage + ", eslClient=" + eslClient);
        }

        return eslMessage;
    }

    private String sendAsyncApiCommand(String command, String arg) {
        Client eslClient = this.getEslClient();

        if (null == eslClient) {
            log.error("sendAsyncApiCommand error, eslClient=" + eslClient + ", command=" + command + ", arg=" + arg + ", host=" + this.getHost() + ", port=" + this.getPort() + ", this=" + this);
            return null;
        }

        EslEvent result = null;
        try {
            result = this.sendBackgroundApiCommand(eslClient, command, arg);
        } catch (Throwable e) {
            throw new RuntimeException("#431 command=" + command + ", host=" + this.getHost() + ", port=" + this.getPort() + ", e=" + e, e);
        }

        if (log.isDebugEnabled()) {
            log.debug(" #435 command=" + command + ", arg=" + arg + ", host=" + this.getHost() + ", port=" + this.getPort() + ", eslMessage=" + result + ", eslClient=" + eslClient);
        }

        List<String> bodyLines = result.getEventBodyLines();
        String firstLine = ListUtil.first(bodyLines);
        return StringUtil.trim(firstLine);
    }

    @Override
    public String getSipUserCallUUID(String user) {
        List<String> listStatus = getChannelsColumns(user, "dest", "uuid");
        if (listStatus == null || listStatus.size() == 0) {
            return null;
        }
        return listStatus.get(0);
    }

    @Override
    public String generateCallUUID() {
        String command = "create_uuid";
        EslMessage message = this.sendSyncApiCommand(command, "");
        if (null == message) {
            log.error("#169 generateCallUUID return null, message=" + message + ", command=" + command);
            return null;
        }
        return message.getBodyLines().get(0);
    }

    @Override
    public String getSipUserCallState(String user) {
        List<String> listStatus = getChannelsColumns(user, "cid_num", "callstate");
        if (listStatus == null || listStatus.size() == 0) {
            listStatus = getChannelsColumns(user, "dest", "callstate");
        }
        if (listStatus == null || listStatus.size() == 0) {
            return null;
        }
        return listStatus.get(0);
    }

    private List<String> getChannelsColumns(String equalStrColumnValue, String equalStrColumn, String valueStrColumn) {
        EslMessage message = this.sendSyncApiCommand("show channels", "");
        if (message == null) {
            return null;
        }

        return getCallsColumn(message, equalStrColumnValue, equalStrColumn, valueStrColumn);
    }

    private static int findColumnIndex(String line, String equalStrColumn) {
        if (!line.contains(line)) {
            return -1;
        }
        String[] array = line.split(",");
        for (int i = 0; i < array.length; i++) {
            String value = array[i];
            if (equalStrColumn.equals(value)) {
                return i;
            }
        }
        return -1;
    }

    private static List<String> getCallsColumn(EslMessage message, String equalStrColumnValue, String equalStrColumn, String valueStrColumn) {

        List<String> bodyLines = message.getBodyLines();
        if (bodyLines.size() < 2) {
            return null;
        }
        List<String> listStatus = new ArrayList<>();
        int equalStrColumnIndex = findColumnIndex(bodyLines.get(0), equalStrColumn);
        int valueStrColumnIndex = findColumnIndex(bodyLines.get(0), valueStrColumn);

        // 舍弃首行头，舍弃最后一行的统计
        for (int i = 1; i < bodyLines.size() - 1; i++) {
            String line = bodyLines.get(i);
            if (!line.contains(equalStrColumnValue)) {
                continue;
            }
            String[] array = line.split(",");
            if (!equalStrColumnValue.equals(array[equalStrColumnIndex])) {
                continue;
            }
            listStatus.add(array[valueStrColumnIndex]);
        }

        return listStatus;
    }

    protected EslEvent sendBackgroundApiCommand(Client eslClient, String command, String arg) throws Throwable {
        throw new RuntimeException();
    }

    protected EslMessage sendApiCommand(Client eslClient, String command, String arg) {
        throw new RuntimeException();
    }

    protected Client connectEslClient(String host, Integer port, String password, int timeout) {
        throw new RuntimeException();
    }
}